/*
 * Copyright 2018-2019 The Apache Software Foundation
 * Modifications 2019 Orient Securities Co., Ltd.
 * Modifications 2019 BoCloud Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.orientsec.grpc.registry.common;



import com.orientsec.grpc.common.constant.GlobalConstants;
import com.orientsec.grpc.registry.common.utils.CollectionUtils;
import com.orientsec.grpc.registry.common.utils.NetUtils;
import com.orientsec.grpc.registry.common.utils.StringUtils;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * URL - Uniform Resource Locator (Immutable, ThreadSafe)
 * <p/>
 * url example:
 * <ul>
 * <li>http://www.facebook.com/friends?param1=value1&amp;param2=value2
 * <li>http://username:password@10.20.130.230:8080/list?version=1.0.0
 * <li>ftp://username:password@192.168.1.7:21/1/read.txt
 * <li>registry://192.168.1.7:9090/com.alibaba.service1?param1=value1&amp;param2=value2
 * </ul>
 * <p/>
 * Some strange example below:
 * <ul>
 * <li>192.168.1.3:20880<br>
 * for this case, url protocol = null, url host = 192.168.1.3, port = 20880, url path = null
 * <li>file:///home/user1/router.js?type=script<br>
 * for this case, url protocol = null, url host = null, url path = home/user1/router.js
 * <li>file://home/user1/router.js?type=script<br>
 * for this case, url protocol = file, url host = home, url path = user1/router.js
 * <li>file:///D:/1/router.js?type=script<br>
 * for this case, url protocol = file, url host = null, url path = D:/1/router.js
 * <li>file:/D:/1/router.js?type=script<br>
 * same as above file:///D:/1/router.js?type=script
 * <li>/home/user1/router.js?type=script <br>
 * for this case, url protocol = null, url host = null, url path = home/user1/router.js
 * <li>home/user1/router.js?type=script <br>
 * for this case, url protocol = null, url host = home, url path = user1/router.js
 * </ul>
 * @author heiden
 * @since 2018/3/15
 */
public final class URL implements Serializable {
  private static final long serialVersionUID = -1985165475234910535L;

  private final String protocol;
  private final String username;
  private final String password;
  private final String host;
  private final int port;
  private final String path;
  private final Map<String, String> parameters;
  private String id;

  private transient volatile  Map<String, Number> numbers;
  private transient volatile  Map<String, URL> urls;
  private transient volatile  String ip;
  private transient volatile  String full;
  private transient volatile  String identity;
  private transient volatile  String parameter;
  private transient volatile  String string;


  protected URL() {
    this.protocol = null;
    this.username = null;
    this.password = null;
    this.host = null;
    this.port = 0;
    this.path = null;
    this.parameters = null;
    this.id = null;
  }

  public URL(String protocol, String host, int port) {
    this(protocol, null, null, host, port, null, (Map<String, String>) null);
  }


  public URL(String protocol, String host, int port, String[] pairs) { // 变长参数...与下面的path参数冲突，改为数组
    this(protocol, null, null, host, port, null, CollectionUtils.toStringMap(pairs));
  }

  /**
   * @since V1.1 2017-4-7 modify by sxp 如果parameters含有interface属性，将属性值赋值给path
   */
  public URL(String protocol, String host, int port, Map<String, String> parameters) {
    this(protocol, null, null, host, port, getPathFromParameters(parameters), parameters);
  }


  /**
   * 从parameters中取interface的属性值赋值给path
   *
   * @return 如果parameters中没有改属性，返回null
   * @author sxp
   * @since V1.0 2017-4-7
   */
  private static String getPathFromParameters(Map<String, String> parameters) {
    String path = null;
    String key = GlobalConstants.CommonKey.INTERFACE;
    if (parameters != null && parameters.containsKey(key)) {
      path = parameters.get(key);
    }
    return path;
  }

  public URL(String protocol, String host, int port, String path) {
    this(protocol, null, null, host, port, path, (Map<String, String>) null);
  }

  public URL(String protocol, String host, int port, String path, String... pairs) {
    this(protocol, null, null, host, port, path, CollectionUtils.toStringMap(pairs));
  }

  public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
    this(protocol, null, null, host, port, path, parameters);
  }

  public URL(String protocol, String username, String password,
             String host, int port, String path) {
    this(protocol, username, password, host, port, path, (Map<String, String>) null);
  }

  public URL(String protocol, String username, String password,
             String host, int port, String path, String... pairs) {
    this(protocol, username, password, host, port, path, CollectionUtils.toStringMap(pairs));
  }

  /**
   * construct a URL class.
   * @param protocol 协议
   * @param username  用户名
   * @param password  用户密码
   * @param host  主机名
   * @param port   端口
   * @param path   url路径
   * @param parameters  参数组
   */
  public URL(String protocol, String username, String password,
             String host, int port, String path, Map<String, String> parameters) {
    if ((username == null || username.length() == 0)
      && password != null && password.length() > 0) {
      throw new IllegalArgumentException("Invalid url, password without username!");
    }
    this.protocol = protocol;
    this.username = username;
    this.password = password;
    this.host = host;
    this.port = (port < 0 ? 0 : port);
    // trim the beginning "/"
    while (path != null && path.startsWith("/")) {
      path = path.substring(1);
    }
    this.path = path;
    if (parameters == null) {
      parameters = new HashMap<String, String>();
    } else {
      parameters = new HashMap<String, String>(parameters);
    }
    this.parameters = Collections.unmodifiableMap(parameters);
  }

  /**
   * Parse url string.
   *
   * @param url URL string
   * @return URL instance
   * @see URL
   */
  public static URL valueOf(String url) {
    if (url == null || (url = url.trim()).length() == 0) {
      throw new IllegalArgumentException("url == null");
    }
    String protocol = null;
    String username = null;
    String password = null;
    String host = null;
    int port = 0;
    String path = null;
    Map<String, String> parameters = null;
    int i = url.indexOf("?"); // seperator between body and parameters
    if (i >= 0) {
      String[] parts = url.substring(i + 1).split("\\&");
      parameters = new HashMap<String, String>();
      for (String part : parts) {
        part = part.trim();
        if (part.length() > 0) {
          int j = part.indexOf('=');
          if (j >= 0) {
            parameters.put(part.substring(0, j), part.substring(j + 1));
          } else {
            parameters.put(part, part);
          }
        }
      }
      url = url.substring(0, i);
    }
    i = url.indexOf("://");
    if (i >= 0) {
      if (i == 0) {
        throw new IllegalStateException("url missing protocol: \"" + url + "\"");
      }
      protocol = url.substring(0, i);
      url = url.substring(i + 3);
    } else {
      // case: file:/path/to/file.txt
      i = url.indexOf(":/");
      if (i >= 0) {
        if (i == 0) {
          throw new IllegalStateException("url missing protocol: \"" + url + "\"");
        }
        protocol = url.substring(0, i);
        url = url.substring(i + 1);
      }
    }

    i = url.indexOf("/");
    if (i >= 0) {
      path = url.substring(i + 1);
      url = url.substring(0, i);
    }
    i = url.indexOf("@");
    if (i >= 0) {
      username = url.substring(0, i);
      int j = username.indexOf(":");
      if (j >= 0) {
        password = username.substring(j + 1);
        username = username.substring(0, j);
      }
      url = url.substring(i + 1);
    }
    i = url.indexOf(":");
    if (i >= 0 && i < url.length() - 1) {
      port = Integer.parseInt(url.substring(i + 1));
      url = url.substring(0, i);
    }
    if (url.length() > 0) {
      host = url;
    }
    return new URL(protocol, username, password, host, port, path, parameters);
  }

  public String getProtocol() {
    return protocol;
  }

  public String getUsername() {
    return username;
  }

  public String getPassword() {
    return password;
  }

  /**
   * 获取授权.
   * @return 授权
   */
  public String getAuthority() {
    if ((username == null || username.length() == 0)
      && (password == null || password.length() == 0)) {
      return null;
    }
    return (username == null ? "" : username)
      + ":" + (password == null ? "" : password);
  }

  public String getHost() {
    return host;
  }

  /**
   * 获取IP地址.
   * <p/>
   * 请注意：
   * 如果和Socket的地址对比，
   * 或用地址作为Map的Key查找，
   * 请使用IP而不是Host，
   * 否则配置域名会有问题
   *
   * @return ip
   */
  public String getIp() {
    if (ip == null) {
      ip = NetUtils.getIpByHost(host);
    }
    return ip;
  }

  public int getPort() {
    return port;
  }

  public int getPort(int defaultPort) {
    return port <= 0 ? defaultPort : port;
  }

  public String getAddress() {
    return port <= 0 ? host : host + ":" + port;
  }

  public String getBackupAddress() {
    return getBackupAddress(0);
  }

  /**
   * 获取备份地址
   * @param defaultPort 默认端口
   * @return addr string
   */
  public String getBackupAddress(int defaultPort) {
    StringBuilder address = new StringBuilder(appendDefaultPort(getAddress(), defaultPort));
    String[] backups = getParameter(Constants.BACKUP_KEY, new String[0]);
    if (backups != null && backups.length > 0) {
      for (String backup : backups) {
        address.append(",");
        address.append(appendDefaultPort(backup, defaultPort));
      }
    }
    return address.toString();
  }

  public List<URL> getBackupUrls() {
    List<URL> urls = new ArrayList<URL>();
    urls.add(this);
    String[] backups = getParameter(Constants.BACKUP_KEY, new String[0]);
    if (backups != null && backups.length > 0) {
      for (String backup : backups) {
        urls.add(this.setAddress(backup));
      }
    }
    return urls;
  }

  private String appendDefaultPort(String address, int defaultPort) {
    if (address != null && address.length() > 0
      && defaultPort > 0) {
      int i = address.indexOf(':');
      if (i < 0) {
        return address + ":" + defaultPort;
      } else if (Integer.parseInt(address.substring(i + 1)) == 0) {
        return address.substring(0, i + 1) + defaultPort;
      }
    }
    return address;
  }

  public String getPath() {
    return path;
  }

  public String getAbsolutePath() {
    if (path != null && !path.startsWith("/")) {
      return "/" + path;
    }
    return path;
  }

  public URL setProtocol(String protocol) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setUsername(String username) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setPassword(String password) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setAddress(String address) {
    int i = address.lastIndexOf(':');
    String host;
    int port = this.port;
    if (i >= 0) {
      host = address.substring(0, i);
      port = Integer.parseInt(address.substring(i + 1));
    } else {
      host = address;
    }
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setHost(String host) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setPort(int port) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public URL setPath(String path) {
    return new URL(protocol, username, password, host, port, path, getParameters());
  }

  public Map<String, String> getParameters() {
    return parameters;
  }

  public String getParameterAndDecoded(String key) {
    return getParameterAndDecoded(key, null);
  }

  public String getParameterAndDecoded(String key, String defaultValue) {
    return decode(getParameter(key, defaultValue));
  }

  public String getParameter(String key) {
    String value = parameters.get(key);
    if (value == null || value.length() == 0) {
      value = parameters.get(Constants.DEFAULT_KEY_PREFIX + key);
    }
    return value;
  }

  public String getParameter(String key, String defaultValue) {
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return value;
  }

  public String[] getParameter(String key, String[] defaultValue) {
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return Constants.COMMA_SPLIT_PATTERN.split(value);
  }

  private Map<String, Number> getNumbers() {
    if (numbers == null) { // 允许并发重复创建
      numbers = new ConcurrentHashMap<String, Number>();
    }
    return numbers;
  }

  private Map<String, URL> getUrls() {
    if (urls == null) { // 允许并发重复创建
      urls = new ConcurrentHashMap<String, URL>();
    }
    return urls;
  }

  public URL getUrlParameter(String key) {
    URL u = getUrls().get(key);
    if (u != null) {
      return u;
    }
    String value = getParameterAndDecoded(key);
    if (value == null || value.length() == 0) {
      return null;
    }
    u = URL.valueOf(value);
    getUrls().put(key, u);
    return u;
  }

  public double getParameter(String key, double defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.doubleValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    double d = Double.parseDouble(value);
    getNumbers().put(key, d);
    return d;
  }

  public float getParameter(String key, float defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.floatValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    float f = Float.parseFloat(value);
    getNumbers().put(key, f);
    return f;
  }

  public long getParameter(String key, long defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.longValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    long l = Long.parseLong(value);
    getNumbers().put(key, l);
    return l;
  }

  public int getParameter(String key, int defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.intValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    int i = Integer.parseInt(value);
    getNumbers().put(key, i);
    return i;
  }

  public short getParameter(String key, short defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.shortValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    short s = Short.parseShort(value);
    getNumbers().put(key, s);
    return s;
  }

  public byte getParameter(String key, byte defaultValue) {
    Number n = getNumbers().get(key);
    if (n != null) {
      return n.byteValue();
    }
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    byte b = Byte.parseByte(value);
    getNumbers().put(key, b);
    return b;
  }

  public float getPositiveParameter(String key, float defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    float value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public double getPositiveParameter(String key, double defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    double value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public long getPositiveParameter(String key, long defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    long value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public int getPositiveParameter(String key, int defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    int value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public short getPositiveParameter(String key, short defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    short value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public byte getPositiveParameter(String key, byte defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    byte value = getParameter(key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public char getParameter(String key, char defaultValue) {
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return value.charAt(0);
  }

  public boolean getParameter(String key, boolean defaultValue) {
    String value = getParameter(key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return Boolean.parseBoolean(value);
  }

  public boolean hasParameter(String key) {
    String value = getParameter(key);
    return value != null && value.length() > 0;
  }

  public String getMethodParameterAndDecoded(String method, String key) {
    return URL.decode(getMethodParameter(method, key));
  }

  public String getMethodParameterAndDecoded(String method, String key, String defaultValue) {
    return URL.decode(getMethodParameter(method, key, defaultValue));
  }

  public String getMethodParameter(String method, String key) {
    String value = parameters.get(method + "." + key);
    if (value == null || value.length() == 0) {
      return getParameter(key);
    }
    return value;
  }

  public String getMethodParameter(String method, String key, String defaultValue) {
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return value;
  }

  public double getMethodParameter(String method, String key, double defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.intValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    double d = Double.parseDouble(value);
    getNumbers().put(methodKey, d);
    return d;
  }

  public float getMethodParameter(String method, String key, float defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.intValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    float f = Float.parseFloat(value);
    getNumbers().put(methodKey, f);
    return f;
  }

  public long getMethodParameter(String method, String key, long defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.intValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    long l = Long.parseLong(value);
    getNumbers().put(methodKey, l);
    return l;
  }

  public int getMethodParameter(String method, String key, int defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.intValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    int i = Integer.parseInt(value);
    getNumbers().put(methodKey, i);
    return i;
  }

  public short getMethodParameter(String method, String key, short defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.shortValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    short s = Short.parseShort(value);
    getNumbers().put(methodKey, s);
    return s;
  }

  public byte getMethodParameter(String method, String key, byte defaultValue) {
    String methodKey = method + "." + key;
    Number n = getNumbers().get(methodKey);
    if (n != null) {
      return n.byteValue();
    }
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    byte b = Byte.parseByte(value);
    getNumbers().put(methodKey, b);
    return b;
  }

  public double getMethodPositiveParameter(String method, String key, double defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    double value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public float getMethodPositiveParameter(String method, String key, float defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    float value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public long getMethodPositiveParameter(String method, String key, long defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    long value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public int getMethodPositiveParameter(String method, String key, int defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    int value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public short getMethodPositiveParameter(String method, String key, short defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    short value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public byte getMethodPositiveParameter(String method, String key, byte defaultValue) {
    if (defaultValue <= 0) {
      throw new IllegalArgumentException("defaultValue <= 0");
    }
    byte value = getMethodParameter(method, key, defaultValue);
    if (value <= 0) {
      return defaultValue;
    }
    return value;
  }

  public char getMethodParameter(String method, String key, char defaultValue) {
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return value.charAt(0);
  }

  public boolean getMethodParameter(String method, String key, boolean defaultValue) {
    String value = getMethodParameter(method, key);
    if (value == null || value.length() == 0) {
      return defaultValue;
    }
    return Boolean.parseBoolean(value);
  }

  public boolean hasMethodParameter(String method, String key) {
    if (method == null) {
      String suffix = "." + key;
      for (String fullKey : parameters.keySet()) {
        if (fullKey.endsWith(suffix)) {
          return true;
        }
      }
      return false;
    }
    if (key == null) {
      String prefix = method + ".";
      for (String fullKey : parameters.keySet()) {
        if (fullKey.startsWith(prefix)) {
          return true;
        }
      }
      return false;
    }
    String value = getMethodParameter(method, key);
    return value != null && value.length() > 0;
  }

  public boolean isLocalHost() {
    return NetUtils.isLocalHost(host) || getParameter(Constants.LOCALHOST_KEY, false);
  }

  public boolean isAnyHost() {
    return Constants.ANYHOST_VALUE.equals(host) || getParameter(Constants.ANYHOST_KEY, false);
  }

  public URL addParameterAndEncoded(String key, String value) {
    if (value == null || value.length() == 0) {
      return this;
    }
    return addParameter(key, encode(value));
  }

  public URL addParameter(String key, boolean value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, char value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, byte value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, short value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, int value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, long value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, float value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, double value) {
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, Enum<?> value) {
    if (value == null) return this;
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, Number value) {
    if (value == null) return this;
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, CharSequence value) {
    if (value == null || value.length() == 0) return this;
    return addParameter(key, String.valueOf(value));
  }

  public URL addParameter(String key, String value) {
    if (key == null || key.length() == 0
      || value == null || value.length() == 0) {
      return this;
    }
    // 如果没有修改，直接返回。
    if (value.equals(getParameters().get(key))) { // value != null
      return this;
    }

    Map<String, String> map = new HashMap<String, String>(getParameters());
    map.put(key, value);
    return new URL(protocol, username, password, host, port, path, map);
  }

  public URL addParameterIfAbsent(String key, String value) {
    if (key == null || key.length() == 0
      || value == null || value.length() == 0) {
      return this;
    }
    if (hasParameter(key)) {
      return this;
    }
    Map<String, String> map = new HashMap<String, String>(getParameters());
    map.put(key, value);
    return new URL(protocol, username, password, host, port, path, map);
  }

  /**
   * Add parameters to a new url.
   *
   * @param parameters
   * @return A new URL
   */
  public URL addParameters(Map<String, String> parameters) {
    if (parameters == null || parameters.size() == 0) {
      return this;
    }

    boolean hasAndEqual = true;
    for (Map.Entry<String, String> entry : parameters.entrySet()) {
      String value = getParameters().get(entry.getKey());
      if (value == null && entry.getValue() != null || !value.equals(entry.getValue())) {
        hasAndEqual = false;
        break;
      }
    }
    // 如果没有修改，直接返回。
    if (hasAndEqual) return this;

    Map<String, String> map = new HashMap<String, String>(getParameters());
    map.putAll(parameters);
    return new URL(protocol, username, password, host, port, path, map);
  }

  public URL addParametersIfAbsent(Map<String, String> parameters) {
    if (parameters == null || parameters.size() == 0) {
      return this;
    }
    Map<String, String> map = new HashMap<String, String>(parameters);
    map.putAll(getParameters());
    return new URL(protocol, username, password, host, port, path, map);
  }

  public URL addParameters(String... pairs) {
    if (pairs == null || pairs.length == 0) {
      return this;
    }
    if (pairs.length % 2 != 0) {
      throw new IllegalArgumentException("Map pairs can not be odd number.");
    }
    Map<String, String> map = new HashMap<String, String>();
    int len = pairs.length / 2;
    for (int i = 0; i < len; i++) {
      map.put(pairs[2 * i], pairs[2 * i + 1]);
    }
    return addParameters(map);
  }

  public URL addParameterString(String query) {
    if (query == null || query.length() == 0) {
      return this;
    }
    return addParameters(StringUtils.parseQueryString(query));
  }

  public URL removeParameter(String key) {
    if (key == null || key.length() == 0) {
      return this;
    }
    return removeParameters(key);
  }

  public URL removeParameters(Collection<String> keys) {
    if (keys == null || keys.size() == 0) {
      return this;
    }
    return removeParameters(keys.toArray(new String[0]));
  }

  public URL removeParameters(String... keys) {
    if (keys == null || keys.length == 0) {
      return this;
    }
    Map<String, String> map = new HashMap<String, String>(getParameters());
    for (String key : keys) {
      map.remove(key);
    }
    if (map.size() == getParameters().size()) {
      return this;
    }
    return new URL(protocol, username, password, host, port, path, map);
  }

  public URL clearParameters() {
    return new URL(protocol, username, password, host, port, path, new HashMap<String, String>());
  }

  public String getRawParameter(String key) {
    if ("protocol".equals(key))
      return protocol;
    if ("username".equals(key))
      return username;
    if ("password".equals(key))
      return password;
    if ("host".equals(key))
      return host;
    if ("port".equals(key))
      return String.valueOf(port);
    if ("path".equals(key))
      return path;
    return getParameter(key);
  }

  public Map<String, String> toMap() {
    Map<String, String> map = new HashMap<String, String>(parameters);
    if (protocol != null)
      map.put("protocol", protocol);
    if (username != null)
      map.put("username", username);
    if (password != null)
      map.put("password", password);
    if (host != null)
      map.put("host", host);
    if (port > 0)
      map.put("port", String.valueOf(port));
    if (path != null)
      map.put("path", path);
    return map;
  }

  public String toString() {
    if (string != null) {
      return string;
    }
    return string = buildString(false, true); // no show username and password
  }

  public String toString(String... parameters) {
    return buildString(false, true, parameters); // no show username and password
  }

  public String toIdentityString() {
    if (identity != null) {
      return identity;
    }
    return identity = buildString(true, false); // only return identity message, see the method "equals" and "hashCode"
  }

  public String toIdentityString(String... parameters) {
    return buildString(true, false, parameters); // only return identity message, see the method "equals" and "hashCode"
  }

  public String toFullString() {
    if (full != null) {
      return full;
    }
    return full = buildString(true, true);
  }

  public String toFullString(String... parameters) {
    return buildString(true, true, parameters);
  }

  public String toParameterString() {
    if (parameter != null) {
      return parameter;
    }
    return parameter = toParameterString(new String[0]);
  }

  public String toParameterString(String... parameters) {
    StringBuilder buf = new StringBuilder();
    buildParameters(buf, false, parameters);
    return buf.toString();
  }

  private void buildParameters(StringBuilder buf, boolean concat, String[] parameters) {
    if (getParameters() != null && getParameters().size() > 0) {
      List<String> includes = (parameters == null || parameters.length == 0 ? null : Arrays.asList(parameters));
      boolean first = true;
      for (Map.Entry<String, String> entry : new TreeMap<String, String>(getParameters()).entrySet()) {
        if (entry.getKey() != null && entry.getKey().length() > 0
          && (includes == null || includes.contains(entry.getKey()))) {
          if (first) {
            if (concat) {
              buf.append("?");
            }
            first = false;
          } else {
            buf.append("&");
          }
          buf.append(entry.getKey());
          buf.append("=");
          buf.append(entry.getValue() == null ? "" : entry.getValue().trim());
        }
      }
    }
  }

  private String buildString(boolean appendUser, boolean appendParameter, String... parameters) {
    return buildString(appendUser, appendParameter, false, false, parameters);
  }

  private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, boolean useService, String... parameters) {
    StringBuilder buf = new StringBuilder();
    if (protocol != null && protocol.length() > 0) {
      buf.append(protocol);
      buf.append("://");
    }
    if (appendUser && username != null && username.length() > 0) {
      buf.append(username);
      if (password != null && password.length() > 0) {
        buf.append(":");
        buf.append(password);
      }
      buf.append("@");
    }
    String host;
    if (useIP) {
      host = getIp();
    } else {
      host = getHost();
    }
    if (host != null && host.length() > 0) {
      buf.append(host);
      if (port > 0) {
        buf.append(":");
        buf.append(port);
      }
    }
    String path;
    if (useService) {
      path = getServiceKey();
    } else {
      path = getPath();
    }
    if (path != null && path.length() > 0) {
      buf.append("/");
      buf.append(path);
    }
    if (appendParameter) {
      buildParameters(buf, true, parameters);
    }
    return buf.toString();
  }

  public java.net.URL toJavaURL() {
    try {
      return new java.net.URL(toString());
    } catch (MalformedURLException e) {
      throw new IllegalStateException(e.getMessage(), e);
    }
  }

  public InetSocketAddress toInetSocketAddress() {
    return new InetSocketAddress(host, port);
  }

  public String getServiceKey() {
    String inf = getServiceInterface();
    if (inf == null) return null;
    StringBuilder buf = new StringBuilder();
    String group = getParameter(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
      buf.append(group).append("/");
    }
    buf.append(inf);
    String version = getParameter(Constants.VERSION_KEY);
    if (version != null && version.length() > 0) {
      buf.append(":").append(version);
    }
    return buf.toString();
  }

  public String toServiceString() {
    return buildString(true, false, true, true);
  }

  @Deprecated
  public String getServiceName() {
    return getServiceInterface();
  }

  public String getServiceInterface() {
    return getParameter(Constants.INTERFACE_KEY, path);
  }

  public URL setServiceInterface(String service) {
    return addParameter(Constants.INTERFACE_KEY, service);
  }

  /**
   * @see #getParameter(String, int)
   * @deprecated Replace to <code>getParameter(String, int)</code>
   */
  @Deprecated
  public int getIntParameter(String key) {
    return getParameter(key, 0);
  }

  /**
   * @see #getParameter(String, int)
   * @deprecated Replace to <code>getParameter(String, int)</code>
   */
  @Deprecated
  public int getIntParameter(String key, int defaultValue) {
    return getParameter(key, defaultValue);
  }

  /**
   * @see #getPositiveParameter(String, int)
   * @deprecated Replace to <code>getPositiveParameter(String, int)</code>
   */
  @Deprecated
  public int getPositiveIntParameter(String key, int defaultValue) {
    return getPositiveParameter(key, defaultValue);
  }

  /**
   * @see #getParameter(String, boolean)
   * @deprecated Replace to <code>getParameter(String, boolean)</code>
   */
  @Deprecated
  public boolean getBooleanParameter(String key) {
    return getParameter(key, false);
  }

  /**
   * @see #getParameter(String, boolean)
   * @deprecated Replace to <code>getParameter(String, boolean)</code>
   */
  @Deprecated
  public boolean getBooleanParameter(String key, boolean defaultValue) {
    return getParameter(key, defaultValue);
  }

  /**
   * @see #getMethodParameter(String, String, int)
   * @deprecated Replace to <code>getMethodParameter(String, String, int)</code>
   */
  @Deprecated
  public int getMethodIntParameter(String method, String key) {
    return getMethodParameter(method, key, 0);
  }

  /**
   * @see #getMethodParameter(String, String, int)
   * @deprecated Replace to <code>getMethodParameter(String, String, int)</code>
   */
  @Deprecated
  public int getMethodIntParameter(String method, String key, int defaultValue) {
    return getMethodParameter(method, key, defaultValue);
  }

  /**
   * @see #getMethodPositiveParameter(String, String, int)
   * @deprecated Replace to <code>getMethodPositiveParameter(String, String, int)</code>
   */
  @Deprecated
  public int getMethodPositiveIntParameter(String method, String key, int defaultValue) {
    return getMethodPositiveParameter(method, key, defaultValue);
  }

  /**
   * @see #getMethodParameter(String, String, boolean)
   * @deprecated Replace to <code>getMethodParameter(String, String, boolean)</code>
   */
  @Deprecated
  public boolean getMethodBooleanParameter(String method, String key) {
    return getMethodParameter(method, key, false);
  }

  /**
   * @see #getMethodParameter(String, String, boolean)
   * @deprecated Replace to <code>getMethodParameter(String, String, boolean)</code>
   */
  @Deprecated
  public boolean getMethodBooleanParameter(String method, String key, boolean defaultValue) {
    return getMethodParameter(method, key, defaultValue);
  }

  public static String encode(String value) {
    if (value == null || value.length() == 0) {
      return "";
    }
    try {
      return URLEncoder.encode(value, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  public static String decode(String value) {
    if (value == null || value.length() == 0) {
      return "";
    }
    try {
      return URLDecoder.decode(value, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((host == null) ? 0 : host.hashCode());
    result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
    result = prime * result + ((password == null) ? 0 : password.hashCode());
    result = prime * result + ((path == null) ? 0 : path.hashCode());
    result = prime * result + port;
    result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
    result = prime * result + ((username == null) ? 0 : username.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    URL other = (URL) obj;
    if (host == null) {
      if (other.host != null)
        return false;
    } else if (!host.equals(other.host))
      return false;
    if (parameters == null) {
      if (other.parameters != null)
        return false;
    } else if (!parameters.equals(other.parameters))
      return false;
    if (password == null) {
      if (other.password != null)
        return false;
    } else if (!password.equals(other.password))
      return false;
    if (path == null) {
      if (other.path != null)
        return false;
    } else if (!path.equals(other.path))
      return false;
    if (port != other.port)
      return false;
    if (protocol == null) {
      if (other.protocol != null)
        return false;
    } else if (!protocol.equals(other.protocol))
      return false;
    if (username == null) {
      if (other.username != null)
        return false;
    } else if (!username.equals(other.username))
      return false;
    return true;
  }

  public String getId() {
    String value = getParameter("id");
    return value;
  }

}
